package plugin.demo.shardingsphere.seata.config.proxy;


import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.sql.DataSource;
import javax.validation.constraints.NotNull;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/4/26 下午7:38
 * @UpdateDate:
 * @Description:
 */
public class ShardingSphereDataSourceProxy implements DataSource{

    /*
    Question: jdbcUrl, username, password 的存在是为了什么，还是无意义值？
     answer：
        ShardingSphere-JDBC对于分布式事务的支持抽象了统一的API, 对于XA模式下JTA的实现基于SPI,
        该类的出现是为了XAShardingTransactionManager的构建，
         通过装饰者模式将各种个样的DataSource装饰成DataSourceWrapper(统一规范):
         org.apache.shardingsphere.transaction.xa.jta.datasource.swapper.DataSourceSwapper.
         在通过Wrapper创建统一标准的ShardingSphereTransactionManager，获取Wrapper会通过SPI的形式
         获取数据源的DataSourcePropertyProvider
         (该类会提供各种数据的核心属性，例如url,username,password的引用，以及数据库连接池的驱动类driverCls),
         而DataSourcePropertyProvider通过SPI的方式动态扩展，创建ShardingSphereTransactionManager过程会
         用过反射的形式读取源数据源的url,username,password但不会实际创建连接.

         private Map<String, Object> getDatabaseAccessConfiguration(final DataSource dataSource) {
            Map<String, Object> result = new HashMap<>(3, 1);
            DataSourcePropertyProvider provider = DataSourcePropertyProviderLoader.getProvider(dataSource);
            try {
                result.put("url", findGetterMethod(dataSource, provider.getURLPropertyName()).invoke(dataSource));
                result.put("user", findGetterMethod(dataSource, provider.getUsernamePropertyName()).invoke(dataSource));
                result.put("password", findGetterMethod(dataSource, provider.getPasswordPropertyName()).invoke(dataSource));
                return result;
            } catch (final ReflectiveOperationException ex) {
                throw new ShardingSphereException("Cannot swap data source type: `%s`, please provide an implementation from SPI `%s`",
                    dataSource.getClass().getName(), DataSourcePropertyProvider.class.getName());
            }
        }
     Question: 为什么只对聚合后的ShardingSphereDataSource创建代理，而不对数据分片组下的HikariDataSource创建代理呢？
       answer: 1.HikariDataSource 默认存在jdbcUrl,username,password的数据，
                 且存在默认的SPI: HikariCPPropertyProvider, 创建TransactionManager时反射无异常.

               2.数据分片二次聚合后对于ShardingSphere提供的实际是聚合后的ShardingSphereDataSource,而非HikariDataSource，
                 一次你需要创建DataSourcePropertiesProvider(存在Wrapper类), 以及代理(满足创建TransactionManager过程的Wrapper反射).
     *
     * */

    private final DataSource dataSource;
    @Getter @Setter
    private String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/mysql";
    @Getter @Setter
    private String username = "USERNAME_AS_USUALLY";
    @Getter @Setter
    private String password = "PASSWORD_AS_USUALLY";

    public ShardingSphereDataSourceProxy(){
        this.dataSource = null;
    }

    private ShardingSphereDataSourceProxy(@NotNull DataSource dataSource){
        this.dataSource = dataSource;
    }

    public static ShardingSphereDataSourceProxy create(@NotNull DataSource dataSource){
        return new ShardingSphereDataSourceProxy(dataSource);
    }

    @Override
    public Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return dataSource.getConnection(username,password);
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return dataSource.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return dataSource.isWrapperFor(iface);
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return dataSource.getLogWriter();
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        dataSource.setLogWriter(out);
    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        dataSource.setLoginTimeout(seconds);
    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return dataSource.getLoginTimeout();
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return dataSource.getParentLogger();
    }
}
